import request, { AxiosPromise, AxiosResponse } from 'axios'
import JSZip from 'jszip'
import FileSaver from 'file-saver'

/**
 * 文件接口
 */
export interface IFile {
  url: string // 文件下载路径
  name: string // 文件名
  suffix: string // 文件后缀
  responseType?: string // 响应流类型
}

/**
 * 文件实现类接口
 */
export interface IFileBase extends IFile {
  data: any // 已经下载好的实体
  loading: boolean // 是否下载中
  loaded: number // 已下载
  total: number // 总大小
  progress: string // 进度百分比
  get(): Promise<any>
  load(): this
}

/**
 * 文件实现类
 */
export class File implements IFileBase {
  private _url: string = ''
  private _name: string = ''
  private _suffix: string = '.txt'
  private _responseType: string = 'blob'
  private _data?: any
  private _loading: boolean = false
  private _loaded: number = 0
  private _total: number = 0
  private _response?: AxiosPromise<any>

  get url() {
    return this._url
  }

  get name() {
    return this._name
  }

  get suffix() {
    return this._suffix
  }

  get responseType() {
    return this._responseType
  }

  get data() {
    return this._data
  }

  get loading() {
    return this._loading
  }

  get loaded() {
    return this._loaded
  }

  get total() {
    return this._total
  }

  get progress() {
    return (this.loaded * 100 / this.total || 1).toFixed(2) + '%'
  }

  get fileName() {
    return `${this.name}.${this.suffix}`
  }

  /**
   * 文件加载
   */
  public load() {
    this._loading = true
    const self = this
    this._response = request({
      method: 'get',
      url: this.url,
      responseType: this.responseType,
      onDownloadProgress(e: any) {
        self._loaded = e.loaded
        self._total = e.total
      }
    }).finally(() => {
      this._loading = false
    })
    return this
  }

  /**
   * 文件数据获取
   */
  public async get() {
    if (!this._response) {
      this.load()
    }
    const data: AxiosResponse<any> = await this._response || {} as AxiosResponse<any>
    this._data = data.data
    return this._data
  }

  /**
   * 文件下载
   * @param cancel 是否使用缓存
   */
  public async download(cancel: boolean = true) {
    // 若使用缓存或者没有数据则重新执行加载方法
    if (!cancel) {
      this.load()
    }
    await this.get()
    FileSaver.saveAs(this.data as any, this.fileName)
    return true
  }

  /**
   * 文件构造器
   * @param params 文件信息
   * @param lazy 禁止预加载
   */
  constructor(params: IFile, lazy: boolean = true) {
    this._url = params.url
    this._name = params.name
    this._suffix = params.suffix
    this._responseType = params.responseType || 'blob'
    if (!lazy) {
      this.load()
    }
  }
}

/**
 * 文件批量下载
 */
export class BatchFile {
  private _files: File[] = []

  get files() {
    return this._files
  }

  get loading() {
    return this._files.some((file: File) => file.loading)
  }

  get downloadingFiles() {
    const files = this._files.filter((file: File) => file.loading)
    return files
  }

  public checkExist(name: string) {
    return this._files.some((file: File) => file.name === name)
  }

  public add(file: IFile) {
    const f = new File(file)
    this._files.push(f)
    f.download()
  }

  public clear() {
    this._files = []
  }

  public addBatch(files: IFile[]) {
    files.map((file: IFile) => {
      if (!this.checkExist(file.name)) {
        this.add(file)
      }
    })
  }
}

/**
 * 文件打包器
 */
export default class Pack {
  private _name: string = new Date().getTime() + ''
  private _files: File[] = []
  private _packing: boolean = false

  get name() {
    return this._name
  }

  get total() {
    return this.files.reduce((result: number, file: File) => {
      return result + file.total
    }, 0)
  }

  get loaded() {
    return this.files.reduce((result: number, file: File) => {
      return result + file.total
    }, 0)
  }

  get progress() {
    return (this.loaded * 100 / this.total || 1).toFixed(2) + '%'
  }

  get loading() {
    return this.files.some((file: File) => file.loading)
  }

  get packing() {
    return this._packing
  }

  get files() {
    return this._files
  }

  /**
   * zip打包
   */
  public async zip() {
    const zip = new JSZip()
    const promises = this.files.map(async(file: File) => {
      await file.get()
      zip.file(file.fileName, file.data, { binary: true })
    })
    await Promise.all(promises)
    this._packing = true
    const content = await zip.generateAsync({type: 'blob'}).finally(() => {
      this._packing = false
    })
    FileSaver.saveAs(content, `${this.name}.zip`)
    return true
  }

  constructor(files: IFile[], name?: string) {
    this._files = files.map((file: IFile) => new File(file))
    this._name = name || new Date().getTime() + ''
  }
}
